﻿using System;
using System.Windows.Controls;
using System.Windows.Navigation;
using JetBrains.Annotations;
using MugenMvvmToolkit.Interfaces;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     A basic implementation of <see cref="INavigationService" /> to adapt the <see cref="Frame" />.
    /// </summary>
    public class FrameNavigationService : INavigationService
    {
        #region Fields

        private readonly Frame _frame;
        private readonly bool _useUrlNavigation;
        private readonly Func<Type, object> _viewFactory;
        private IViewPageMappingItem _rootUri;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="FrameNavigationService" /> class.
        /// </summary>
        public FrameNavigationService([NotNull]Frame frame, bool useUrlNavigation, Func<Type, object> viewFactory)
        {
            Should.NotBeNull(frame, "frame");
            if (!useUrlNavigation)
                Should.NotBeNull(viewFactory, "viewFactory");
            _frame = frame;
            _useUrlNavigation = useUrlNavigation;
            _viewFactory = viewFactory;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="FrameNavigationService" /> class.
        /// </summary>
        public FrameNavigationService([NotNull]Frame frame, bool useUrlNavigation, Func<Type, object> viewFactory, IViewPageMappingItem rootItem)
        {
            Should.NotBeNull(frame, "frame");
            if (!useUrlNavigation)
                Should.NotBeNull(viewFactory, "viewFactory");
            RootItem = rootItem;
            _frame = frame;
            _useUrlNavigation = useUrlNavigation;
            _viewFactory = viewFactory;
        }

        #endregion

        #region Implementation of INavigationService

        /// <summary>
        ///     Gets or sets the root page <see cref="IViewPageMappingItem" />
        /// </summary>
        public IViewPageMappingItem RootItem
        {
            get
            {
                Should.PropertyBeNotNull(_rootUri, "RootItem");
                return _rootUri;
            }
            set
            {
                Should.PropertyBeNotNull(value, "RootItem");
                _rootUri = value;
            }
        }

        /// <summary>
        ///     The <see cref="Uri" /> source.
        /// </summary>
        public Uri Source
        {
            get { return _frame.Source; }
            set { _frame.Source = value; }
        }

        /// <summary>
        ///     Indicates whether the navigator can navigate back.
        /// </summary>
        public bool CanGoBack
        {
            get { return _frame.CanGoBack; }
        }

        /// <summary>
        ///     Indicates whether the navigator can navigate forward.
        /// </summary>
        public bool CanGoForward
        {
            get { return _frame.CanGoForward; }
        }

        /// <summary>
        ///     The current <see cref="Uri" /> source.
        /// </summary>
        public Uri CurrentSource
        {
            get { return _frame.CurrentSource; }
        }

        /// <summary>
        ///     The current content.
        /// </summary>
        public object CurrentContent
        {
            get { return _frame.Content; }
        }

        /// <summary>
        ///     Stops the loading process.
        /// </summary>
        public void StopLoading()
        {
            _frame.StopLoading();
        }

        /// <summary>
        ///     Navigates back.
        /// </summary>
        public void GoBack()
        {
            _frame.GoBack();
        }

        /// <summary>
        ///     Navigates forward.
        /// </summary>
        public void GoForward()
        {
            _frame.GoForward();
        }

        /// <summary>
        ///     Removes the most recent entry from the back stack.
        /// </summary>
        /// <returns> The entry that was removed. </returns>
        public JournalEntry RemoveBackEntry()
        {
            var navigationService = ((Page)_frame.Content).NavigationService;
            if (navigationService != null)
                return navigationService.RemoveBackEntry();
            return null;
        }

        /// <summary>
        ///     Displays the content located at the specified <see cref="IViewPageMappingItem" />.
        /// </summary>
        /// <param name="uri">
        ///     The <c>Uri</c> of the content to display.
        /// </param>
        /// <param name="extraData">A <see cref="T:System.Object"/> that contains data to be used for processing during navigation.</param>
        /// <returns>
        ///     <c>true</c> if the content was successfully displayed; otherwise, <c>false</c>.
        /// </returns>
        public bool Navigate(Uri uri, object extraData = null)
        {
            Should.NotBeNull(uri, "uri");
            if (extraData != null)
                return _frame.Navigate(uri, extraData);
            return _frame.Navigate(uri);
        }

        /// <summary>
        ///     Displays the content located at the specified <see cref="IViewPageMappingItem" />.
        /// </summary>
        /// <param name="source">
        ///     The <c>IViewPageMappingItem</c> of the content to display.
        /// </param>
        /// <param name="extraData">A <see cref="T:System.Object"/> that contains data to be used for processing during navigation.</param>
        /// <returns>
        ///     <c>true</c> if the content was successfully displayed; otherwise, <c>false</c>.
        /// </returns>
        public bool Navigate(IViewPageMappingItem source, object extraData = null)
        {
            Should.NotBeNull(source, "source");
            if (_useUrlNavigation)
                return Navigate(source.Uri, extraData);
            if (extraData == null)
                return _frame.Navigate(_viewFactory(source.ViewType));
            return _frame.Navigate(_viewFactory(source.ViewType), extraData);
        }

        /// <summary>
        ///     Displays the content located at the specified <see cref="IViewPageMappingItem" />.
        /// </summary>
        /// <param name="source">
        ///     The content to display.
        /// </param>
        /// <param name="extraData">A <see cref="T:System.Object"/> that contains data to be used for processing during navigation.</param>
        /// <returns>
        ///     <c>true</c> if the content was successfully displayed; otherwise, <c>false</c>.
        /// </returns>
        public bool Navigate(object source, object extraData = null)
        {
            Should.NotBeNull(source, "source");
            if (extraData != null)
                return _frame.Navigate(source, extraData);
            return _frame.Navigate(source);
        }

        /// <summary>
        ///     Navigates to the root page.
        /// </summary>
        public bool NavigateToRoot()
        {
            if (_useUrlNavigation)
                return _frame.Navigate(RootItem.Uri);
            return _frame.Navigate(_viewFactory(RootItem.ViewType));
        }

        /// <summary>
        ///     Raised after navigation.
        /// </summary>
        public event NavigatedEventHandler Navigated
        {
            add { _frame.Navigated += value; }
            remove { _frame.Navigated -= value; }
        }

        /// <summary>
        ///     Raised prior to navigation.
        /// </summary>
        public event NavigatingCancelEventHandler Navigating
        {
            add { _frame.Navigating += value; }
            remove { _frame.Navigating -= value; }
        }

        /// <summary>
        ///     Raised when navigation fails.
        /// </summary>
        public event NavigationFailedEventHandler NavigationFailed
        {
            add { _frame.NavigationFailed += value; }
            remove { _frame.NavigationFailed -= value; }
        }

        /// <summary>
        ///     Raised when navigation is stopped.
        /// </summary>
        public event NavigationStoppedEventHandler NavigationStopped
        {
            add { _frame.NavigationStopped += value; }
            remove { _frame.NavigationStopped -= value; }
        }

        /// <summary>
        ///     Raised when a fragment navigation occurs.
        /// </summary>
        public event FragmentNavigationEventHandler FragmentNavigation
        {
            add { _frame.FragmentNavigation += value; }
            remove { _frame.FragmentNavigation -= value; }
        }

        #endregion
    }
}